package com.castrianni.vcs.svn;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Collections;

import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.history.SvnFileRevision;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNRevision;
import com.castrianni.vcs.VcsBranch;
import com.castrianni.vcs.VcsRevisionGraph;
import com.castrianni.vcs.VcsRevisionGraphRevision;
import com.castrianni.vcs.VcsRevisionGraphProjectComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.history.VcsHistoryProvider;

public class SVNRevisionGraph extends VcsRevisionGraph {
  public SVNRevisionGraph(Project project,FilePath filePath,AbstractVcs vcs,VcsHistoryProvider historyProvider)
  {
    super(project,filePath,vcs,historyProvider);
    init();
  }

  protected String getAllRevisions()
  {
    final SvnVcs svcs = (SvnVcs)_vcs;
    final Throwable throwable[] = new Throwable[1];
    _revisions = new ArrayList<VcsRevisionGraphRevision>();
    _revMap = new LinkedHashMap<VcsBranch,ArrayList<VcsRevisionGraphRevision>>();
    _branchMap = new LinkedHashMap<String,VcsBranch>();
    VcsRevisionGraphProjectComponent rgpc = _project.getComponent(VcsRevisionGraphProjectComponent.class);
    final String projectRootLocation = rgpc.get_projectRoot();
    final String trunkRepoLocation = rgpc.get_trunk();
    final String branchesRepoLocation = rgpc.get_branches();
    final String tagsRepoLocation = rgpc.get_tags();
    final SVNInfo info = svcs.getCachedInfo(_filePath.getVirtualFile()).getInfo();
    Runnable command = new Runnable() {
      public void run() {
        final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
        if(indicator != null)
          indicator.setText("Collecting history with branches for file: " + _filePath.getName());
        try {
          String repoURL = projectRootLocation;
          if(repoURL.equals(VcsRevisionGraphProjectComponent.DEFAULT_PROJECT_ROOT))
            repoURL = info.getRepositoryRootURL().toString();
          String fileURL = info.getURL().toString();
          //Obtain relative path of current file
          String relativeFileURL = null;
          if(fileURL.startsWith(SVNPathUtil.append(repoURL,trunkRepoLocation)))
            relativeFileURL = fileURL.substring(SVNPathUtil.append(repoURL,trunkRepoLocation).length() + 1);
          else if(fileURL.startsWith(SVNPathUtil.append(repoURL,branchesRepoLocation))) {
            relativeFileURL = fileURL.substring(SVNPathUtil.append(repoURL,branchesRepoLocation).length() + 1);
            //Remove branch name
            relativeFileURL = SVNPathUtil.removeHead(relativeFileURL);
          } else if(fileURL.startsWith(SVNPathUtil.append(repoURL,tagsRepoLocation))) {
            relativeFileURL = fileURL.substring(SVNPathUtil.append(repoURL,tagsRepoLocation).length());
            //Remove tag name
            relativeFileURL = SVNPathUtil.removeHead(relativeFileURL);
          }
          SVNRepository srepo = svcs.createRepository(repoURL);
          //noinspection unchecked
          Collection<SVNDirEntry> branchDirs = srepo.getDir(branchesRepoLocation,srepo.getLatestRevision(),null,(Collection)null);
          HashMap<String,String> logFiles = new HashMap<String,String>();
          logFiles.put(trunkRepoLocation,SVNPathUtil.append(trunkRepoLocation,relativeFileURL));
          for(SVNDirEntry branchDir : branchDirs) {
            String branchDirPrefix = SVNPathUtil.append(branchesRepoLocation,branchDir.getName());
            logFiles.put(branchDir.getName(),SVNPathUtil.append(branchDirPrefix,relativeFileURL));
          }
          ArrayList<VcsBranch> branchList = new ArrayList<VcsBranch>();
          String targetPaths[] = new String[1];
          for(String branchName : logFiles.keySet()) {
            VcsBranch branch = new VcsBranch(branchName,branchName);
            branchList.add(branch);
            targetPaths[0] = logFiles.get(branchName);
            Collection<SVNLogEntry> logEntries;
            try {
              //noinspection unchecked
              logEntries = srepo.log(targetPaths,null,1,-1,true,true);
            } catch(Throwable t) {
              continue;
            }
            for(SVNLogEntry logEntry : logEntries) {
              Date date = logEntry.getDate();
              String author = logEntry.getAuthor();
              String message = logEntry.getMessage();
              SVNRevision rev = SVNRevision.create(logEntry.getRevision());
              SvnFileRevision svnRev = new SvnFileRevision(svcs,info.getRevision(),rev,info.getURL().toString(),author,date,message);
              VcsRevisionGraphRevision rgRev = new VcsRevisionGraphRevision(svnRev,branchName);
              _revisions.add(rgRev);
              //Check for branch points
              //noinspection unchecked
              Map<String,SVNLogEntryPath> changedPaths = logEntry.getChangedPaths();
              for(SVNLogEntryPath logEntryPath : changedPaths.values()) {
                String fromBranchName = logEntryPath.getCopyPath();
                long fromBranchRevision = logEntryPath.getCopyRevision();
                if(fromBranchName != null) {
                  if(fromBranchName.contains(trunkRepoLocation))
                    fromBranchName = trunkRepoLocation;
                  else if(fromBranchName.contains(branchesRepoLocation)) {
                    int index = fromBranchName.indexOf(branchesRepoLocation);
                    fromBranchName = fromBranchName.substring(index + branchesRepoLocation.length() + 1);
                    fromBranchName = SVNPathUtil.head(fromBranchName);
                  }
                  branch.setParentBranchId(fromBranchName);
                  branch.setParentBranchPointRevision("" + fromBranchRevision);
                  break;
                }
              }
              if(indicator != null)
                indicator.setText2("Revision " + logEntry.getRevision() + " processed");
            }
          }
          //Sort branches based on branchPointRevision
          Collections.sort(branchList,new SVNBranchCompare());
          //Build branchMap in proper order
          for(VcsBranch branch : branchList)
            _branchMap.put(branch.getId(),branch);
          //Build revMap in proper order
          HashMap<String,ArrayList<VcsRevisionGraphRevision>> revMap = new HashMap<String,ArrayList<VcsRevisionGraphRevision>>();
          for(VcsRevisionGraphRevision rev : _revisions) {
            String branchId = rev.getParentBranchId();
            ArrayList<VcsRevisionGraphRevision> revsOnBranch = revMap.get(branchId);
            if(revsOnBranch == null) {
              revsOnBranch = new ArrayList<VcsRevisionGraphRevision>();
              revMap.put(branchId,revsOnBranch);
            }
            revsOnBranch.add(rev);
          }
          //Sort everything
          for(ArrayList<VcsRevisionGraphRevision> revsOnBranch : revMap.values())
            Collections.sort(revsOnBranch,_revCompare);
          //Fill up revMap in correct order
          for(String branchId : _branchMap.keySet()) {
            VcsBranch branch = _branchMap.get(branchId);
            ArrayList<VcsRevisionGraphRevision> revsOnBranch = revMap.remove(branchId);
            _revMap.put(branch,revsOnBranch);
          }
        } catch(Throwable t) {
          throwable[0] = t;
        }
      }
    };
    if(ApplicationManager.getApplication().isDispatchThread())
      ProgressManager.getInstance().runProcessWithProgressSynchronously(command,"Revision History",false,svcs.getProject());
    else
      command.run();
    if(throwable[0] != null)
      throw new RuntimeException(throwable[0].getMessage());
    return(info.getCommittedRevision().toString());
  }

  protected class SVNBranchCompare implements Comparator<VcsBranch> {
    public int compare(VcsBranch b1,VcsBranch b2)
    {
      if((b1 == null) && (b2 == null))
        return(0);
      if((b1 == null))
        return(-1);
      if((b2 == null))
        return(1);
      Long revNum1;
      try {
        revNum1 = Long.parseLong(b1.getParentBranchPointRevision());
      } catch(Throwable t) {
        revNum1 = null;
      }
      Long revNum2;
      try {
        revNum2 = Long.parseLong(b2.getParentBranchPointRevision());
      } catch(Throwable t) {
        revNum2 = null;
      }
      if((revNum1 == null) && (revNum2 == null))
        return(0);
      if((revNum1 == null))
        return(-1);
      if((revNum2 == null))
        return(1);
      return(revNum1.compareTo(revNum2));
    }
  }
}
